﻿using K9Nano.Dependency;
using Microsoft.Extensions.Options;
using NUlid;

namespace K9Nano.FileStorage;

public class PhysicalFileStorage : IFileStorage, ISingletonDependency
{
    private readonly IOptions<K9Options> _optionAccessor;
    private readonly string _tmpRoot;

    public PhysicalFileStorage(IOptions<K9Options> optionAccessor)
    {
        _optionAccessor = optionAccessor;
        Directory.CreateDirectory(optionAccessor.Value.StorageRoot);

        // 临时文件目录通过操作系统的临时文件清理机制完成定时清理
        _tmpRoot = Path.Combine(_optionAccessor.Value.StorageRoot, "tmp");
        Directory.CreateDirectory(_tmpRoot);
    }

    public async Task<string> SaveAsync(Stream stream, string ext, bool leaveOpen = false, CancellationToken cancellation = default)
    {
        const int maxTry = 10;
        try
        {
            var i = 0;
            for (; i < maxTry; i++)
            {
                try
                {
                    var (fullPath, relativePath) = GetUniqueFileName(_optionAccessor.Value.StorageRoot, ext);

                    await using var writer = File.Open(fullPath, FileMode.CreateNew, FileAccess.ReadWrite);
                    await stream.CopyToAsync(writer, cancellation);

                    return relativePath;
                }
                catch (IOException)
                {
                    if (i == maxTry - 1)
                    {
                        // 最后一次
                        throw;
                    }
                }
            }
            throw new IOException("保存文件失败");
        }
        finally
        {
            if (!leaveOpen)
            {
                await stream.DisposeAsync();
            }
        }
    }

    public Task<Stream> OpenReadAsync(string uri, CancellationToken cancellation = default)
    {
        var fullPath = Path.Combine(_optionAccessor.Value.StorageRoot, uri);
        return Task.FromResult<Stream>(File.OpenRead(fullPath));
    }

    /// <summary>
    /// 生成唯一文件路径
    /// </summary>
    private static (string FullPath, string RelativePath) GetUniqueFileName(string path, string ext)
    {
        var now = DateTime.Now;
        var year = now.ToString("yyyy");
        Directory.CreateDirectory(Path.Combine(path, year));
        var month = Path.Combine(year, now.ToString("MM"));
        Directory.CreateDirectory(Path.Combine(path, month));
        var day = Path.Combine(month, now.ToString("dd"));
        Directory.CreateDirectory(Path.Combine(path, day));

        for (var i = 1; i < 1000; i++)
        {
            var fileName = Path.Combine(day, DateTime.Now.ToString("yyyyMMddHHmmssfff") + i.ToString("000") + ext);
            var fullPath = Path.Combine(path, fileName);
            if (!File.Exists(fullPath))
            {
                return (fullPath, fileName);
            }
        }

        // 1000次还没找到
        var guidName = Path.Combine(day, Ulid.NewUlid().ToString() + ext);
        var fullGuidPath = Path.Combine(path, guidName);
        return (fullGuidPath, guidName);
    }

    public string GetTmpFile(string ext)
    {
        while (true)
        {
            var tmpFile = Path.Combine(_tmpRoot, Ulid.NewUlid().ToString() + ext);
            if (File.Exists(tmpFile)) continue;
            return tmpFile;
        }
    }
}